#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <netdb.h>
#include <unistd.h>

#include <sys/ioctl.h>
 #include <net/if.h>




#include "logsFunctions.h"
#include "serverResponse.h"



//globalne----------------------
     int wizyty=0;
     int sockfd;
     int newsockfd;
     const int TimeOut = 10;
     const int ResendAttempts = 3; // how many times will I try to send if there
                                    // is no back response

//-------------------------------

void endServer(int n)
{
    MessagePrint("Zamykam połączenie");
    shutdown(newsockfd, 0);
    MessagePrint("Zamykam serwer");
    shutdown(sockfd, 0);
    MessagePrint("Zamykam bazę danych");
    ShutdownDatabase();
    MessagePrint("Zamykam log");
    MessageClose();

    exit(0);
    //return;
}

void endClient(int n)
{
    MessagePrint("Klient nie odpowiada");
    //return;
}

int getSizeOf(char * sentence)
{
    int count = 0;
    while(sentence[count]!='\0')
    {
        count++;
    }

    return count+1;
}

void *ClientRequest(void *arg)
{
        int mysockfd = (int) arg;
        char bufor[256];
        char * answer;
        char returnAnswer[20];

        char childMessage[200];
        sprintf(childMessage,"Uruchomiono nowy wątek dla klienta : %ld",
                (long)pthread_self());

	MessagePrint(childMessage);

        int n=1;
        int log = 0;
        int i=0;
        int respondOk=0;
        int wrongResponse=0;
        int attempt=0;
        int *loginState = &log;
        int res=1;
        int *responseNeeded = &res;  // it will indicate, if a response is needed

        signal(SIGPIPE,endClient);
        
        while (n > 0)
        {
            // setting recv timeout
            struct timeval tv; /* timeval and timeout stuff added by davekw7x */
            tv.tv_sec = 300;
            tv.tv_usec = 0;
            if (setsockopt(mysockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof(tv) ))
            {
                MessagePrint("Błąd przy ustawieniu Timeout");
                return -1;
            }

            do
            {
                bzero(bufor,256);
                n = recv(mysockfd, bufor, sizeof(bufor), 0);
            } while ((bufor[0] == 'O') && (bufor[1] == 'K')); // ignore OK - it's a response, not a command

            if ((n > 0) && (bufor[0]!='\0'))
            {
                sprintf(childMessage, "Klient %ld przysłał : \t%s\n",
                        (long) pthread_self(),
                        bufor);
                printf(childMessage);
                answer = GetServerResponse(bufor,loginState,responseNeeded);
                int ln=strlen(answer);
                answer[ln++]='\n';
                answer[ln]='\0';
                // retrieve wanted answer from client - it's the OK# part
                i=0;
                while(answer[i]!=' ' && answer[i]!='\0')
                {
                    returnAnswer[i] = answer[i];
                    i++;
                }

                if(returnAnswer[0]=='K' && returnAnswer[1]=='O') //KO doesn't need response
                {
                    *responseNeeded = 0;
                }

                returnAnswer[i] = '\0';
                // end retrieving

                // Sending and waiting for the right respond
                
                // setting recv timeout
                struct timeval tv; 
                tv.tv_sec = TimeOut;
                tv.tv_usec = 0;
                if (setsockopt(mysockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,  sizeof(tv) ))
                {
                    MessagePrint("Błąd przy ustawieniu Timeout");
                    return (int)-1;
                }
                
                attempt = 0;
                respondOk = 0;
                while(respondOk==0 && attempt < ResendAttempts)
                {
                    if(wrongResponse==0)
                    {
                       sprintf(childMessage, "Wysyłanie odpowiedzi do klienta %ld, próba %d: %s",
                       (long) pthread_self(),attempt+1, answer);
                       
                       MessagePrint(childMessage);
                       n = send(mysockfd, answer, getSizeOf(answer), 0);
                       if(n < 0)
                       {
                           respondOk=0;
                           break;
                       }
                    }
                    else
                    {
                        wrongResponse = 0; // reset the wrongResponse flag
                    }

                    if(responseNeeded==1)
                    {

                       bzero(bufor,256);

                       n = recv(mysockfd, bufor, sizeof(bufor), 0);

                       if ((n > 0) && (bufor[0]!='\0'))
                       {
                           if(strcmp(bufor,returnAnswer) == 0)
                           {
                               respondOk = 1;
                           }
                           else
                           {
                               // There is a connection with the client
                               // but the response is wrong.
                               wrongResponse = 1;
                               send(mysockfd, GetWrongResponse(), (int)11, 0);
                           }
                       }
                       else
                       {
                           attempt++;
                       }
                    }
                    else //if response is not needed
                    {
                        respondOk = 1;
                    }
                }
                
                free(answer);
                
                if(respondOk == 0)
                {
                    sprintf(childMessage,"Klient %ld nie odpowiada",
                    (long)pthread_self());
                    MessagePrint(childMessage);
                    n=0;
                }
            }
        }

       
        sprintf(childMessage,"Klient %ld kończy połączenie", (long)pthread_self());
	MessagePrint(childMessage);
	close(mysockfd);
        MessagePrint("Zakończono połączenie");
	pthread_exit((void *)0);
}

char * getDatabaseConnectionString(char * configFile)
{
    FILE * filePointer;
    filePointer = fopen (configFile,"r");
    char * result = (char *) malloc(100);

    if (filePointer!=NULL)
    {
        fscanf (filePointer, "%s", result);
    }

    return result;
}

int serverLoop()
{
        
        struct sockaddr_in clientAddr;
        int clientAddrLen = sizeof(clientAddr);
        pthread_t childThread;
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

     	listen(sockfd, 5);			//nasłuchiwanie, maksymalnie 5 oczekujacych w kolejce

        newsockfd = accept(sockfd, (struct sockaddr *) &clientAddr, &clientAddrLen);
     	if (newsockfd < 0)
     	{
       	 	MessagePrint("Błąd przy akceptowaniu połączenia");
        	return -1;
    	}
        char polaczenie[200];
        sprintf(polaczenie,"Otwieram połączenie : \n\t%s : %d", inet_ntoa(clientAddr.sin_addr),clientAddr.sin_port);
        MessagePrint(polaczenie);
        
	pthread_create(&childThread, &attr, ClientRequest,  (void *) newsockfd);

        pthread_attr_destroy(&attr);
        return 0;
}

char * getHostInfo(char * interface)
{
/*
    char Buf [200] ;
    struct hostent * Host;
    gethostname(Buf,200);
    //printf ( "%s\n", Buf ) ;
    Host = ( struct hostent * ) gethostbyname ( Buf ) ;
    //printf ( "The name :: %s\n" , Host->h_name ) ;
    
    return inet_ntoa(*((struct in_addr *)Host->h_addr));

*/

        struct ifreq ifr;
        int sock, j, k;
        char *p, addr[32], mask[32], mac[32];
        char * answer = (char*)malloc(150);


        sock=socket(PF_INET, SOCK_STREAM, 0);
        if (-1==sock) {
            sprintf(answer,"Błąd pobrania danych hosta #%d",1);
            return answer;
        }

        strncpy(ifr.ifr_name,interface,sizeof(ifr.ifr_name)-1);
        ifr.ifr_name[sizeof(ifr.ifr_name)-1]='\0';

        if (-1==ioctl(sock, SIOCGIFADDR, &ifr)) {
            sprintf(answer,"Błąd pobrania danych hosta #%d",2);
            return answer;
        }
        p=inet_ntoa(((struct sockaddr_in *)(&ifr.ifr_addr))->sin_addr);
        strncpy(addr,p,sizeof(addr)-1);
        addr[sizeof(addr)-1]='\0';

        if (-1==ioctl(sock, SIOCGIFNETMASK, &ifr)) {
            sprintf(answer,"Błąd pobrania danych hosta #%d",3);
            return answer;
        }
        p=inet_ntoa(((struct sockaddr_in *)(&ifr.ifr_netmask))->sin_addr);
        strncpy(mask,p,sizeof(mask)-1);
        mask[sizeof(mask)-1]='\0';

        if (-1==ioctl(sock, SIOCGIFHWADDR, &ifr)) {
            sprintf(answer,"Błąd pobrania danych hosta #%d",4);
            return answer;
        }
        for (j=0, k=0; j<6; j++) {
            k+=snprintf(mac+k, sizeof(mac)-k-1, j ? ":%02X" : "%02X",
                (int)(unsigned int)(unsigned char)ifr.ifr_hwaddr.sa_data[j]);
        }
        mac[sizeof(mac)-1]='\0';

        sprintf(answer,"[%s] \n\t\tmac: %s \n\t\tip: %s \n\t\tmask: %s",interface,mac,addr,mask);

        close(sock);
        return answer;

}

int serverInit(int nrPortu) {

    struct sockaddr_in serverAddr;
    //signal(SIGUSR1,podajWizyty);
    signal(SIGINT, endServer);
    signal(SIGTERM, endServer);
    char * message = (char *)malloc(200);

    char * connString = getDatabaseConnectionString("config.cfg");

    // Sqlite3 start
    if(SetDatabaseConnection(connString))
    {
        free(connString);
        MessagePrint("Błąd połączenia z bazą");
        return -1;
    }

    free(connString);

    sockfd = socket(AF_INET, SOCK_STREAM, 0); // gniazdko

    if (sockfd < 0) {
        MessagePrint("Błąd podczas otwierania gniazdka");
        return -1;
    }

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(nrPortu);

    int opt = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *) & opt, sizeof (int)) < 0) {
        MessagePrint("Port został już podpięty");
        return -1;
    }

    if (bind(sockfd, (struct sockaddr *) & serverAddr, sizeof (serverAddr)) < 0) //podpięcie
    {
        MessagePrint("Błąd podczas podpięcia do portu");
        return -1;
    }


    int koniec = 0;


    int n = 0;
    MessagePrintClean("------------------------------------------------\n");
    MessagePrint("Rozpoczynam pracę serwera");
    sprintf(message,"Działam na porcie: %d",nrPortu);
    MessagePrint(message);
    sprintf(message,"Dane hosta: %s",getHostInfo("eth0"));
    MessagePrint(message);
    MessagePrintClean("------------------------------------------------\n");


    while (koniec == 0) {
        serverLoop();
    }//k.while
    MessagePrint("Zamykam serwer");
    ShutdownDatabase();
    shutdown(sockfd, 0);
    return 0;
}
